前言

ES Modules 规范是什么

它是在 ECMAScript 6 (ES2015/ES6) 中引入的一项重要特性,旨在取代 CommonJS 和 AMD 规范,成为 JavaScript 模块化的主要标准。

与 CommonJS 规范的区别

ESM 模块的导入和导出遵循 ECMAScript 官方规范,与 CommonJS 不同。ESM 模块的导入使用 import 关键字,导出使用 export 关键字。

默认情况下 Node.js 会将 .js 后缀文件识别为 CJS 模块。

在 Node.js 中正确工作

要让 Node.js 正确识别,主要有两种方式:

json { "type":"module" }

下面是同样 ESM 规范代码,后缀 .mjs.js 的执行结果,前者能被正确执行,后者会报错,

接下来,我们就来详细的看看 ESM 模块的导入导出用法。

1 导入导出

导入导出常见的主要有下面 3 种场景,下面分别介绍。

1.1 默认导入导出

使用 export default 关键字。

js // 文件 export_default.js // 导出默认对象 export default { // 定义 hello 方法,输出欢迎信息 hello(name) { console.log(`Hello, ${name}!`) }, // 定义 byebye 方法,输出道别信息 byebye(name) { console.log(`byebye, ${name}!`) }, // 定义 userInfo 对象,存储用户信息 userInfo: { name: 'forever', // 用户名 age: 18 // 用户年龄 } }

导入默认导出对象。

```js // 文件 import_default.js

// 引入 export_default.js 中默认导出的模块 import defaultModule from './export_default.js'

// 调用 defaultModule 中定义的 byebye() 方法,输出道别信息并传入用户姓名 defaultModule.byebye(defaultModule.userInfo.name)

// 调用 defaultModule 中定义的 hello() 方法,输出欢迎信息并传入用户姓名 defaultModule.hello(defaultModule.userInfo.name)

```

运行。

1.2 具名导入导出

具名导出,使用 export 关键字。

```js // 文件 export.js

// 定义 hello 方法,输出欢迎信息 export function hello(name) { console.log(Hello, ${name}!) }

// 定义 byebye 方法,输出道别信息 export function byebye(name) { console.log(byebye, ${name}!) }

// 定义 userInfo 对象,存储用户信息 export const userInfo = { name: 'forever', // 用户名 age: 18 // 用户年龄 }

```

导入指定具名内容。

```js // 文件 import.js

// 引入 export_named.js 中具名导出的模块 import { byebye, hello, userInfo as user } from './export_named.js'

// 调用 byebye() 方法,输出道别信息并传入用户姓名 byebye(user.name)

// 调用 hello() 方法,输出欢迎信息并传入用户姓名 hello(user.name) ```

实用 as 关键字还可以修改导入内容的名称。

运行结果如下。

1.3 导入导出所有对象

可以将另一个模块的内容直接全部导出。

导出同时也可以设置默认导出。

```js // 文件 export_all.js

// 从 export.js 中导出所有的模块成员 export * from './export.js'

// 导出一个默认模块,对象包含 goal 属性,初始值为 'learn' export default { goal: 'learn' } ```

使用 import * as xx from 'module' 导入所有内容。

```js // 文件 import_all.js

// 导入 export_all.js 中所有被导出的模块成员,并作为 allValues 对象的属性 import * as allValues from './export_all.js'

// 在控制台输出 allValues 对象 console.log(allValues)

// 从 allValues 对象中解构出 hello、byebye、default 和 userInfo 模块成员 const { hello, byebye, default: data, userInfo } = allValues

// 调用 hello() 方法,输出欢迎信息并传入用户姓名 hello(userInfo.name)

// 调用 byebye() 方法,输出道别信息并传入用户姓名 byebye(userInfo.name)

// 输出 data 对象的 goal 属性 console.log(data.goal) ```

下面是运行结果,

其中 export default 的内容,会出现在导入模块的 default 属性上。

1.4 重新导出

某些时候有许多的,一个包内部可能有成百上千个 模块,如果全都采用如下方式,显得不太优雅。

js import { fun1, fun2 } from 'module_demo/hello.js' import { fun3, fun4 } from 'module_demo/util.js' import { fun5, fun6 } from 'module_demo/lib.js'

此时通常会将该模块内部需要导出的方法,统一收拢到一个 index.js 中,对外统一暴露。

下面是具体示例。

有 2 个文件 lib.jsutil.js,如下分别导出了一些方法。

``js // lib.js export function hello(name) { console.log(Hello, ${name}!`) }

export default { filename: 'lib.js', des: 'lib.js的一些默认导出' } ```

``js // util.js export function byebye(name) { console.log(ByeBye, ${name}!`) }

export default { filename: 'util.js', des: 'util.js的一些默认导出' } ```

此时通过 index.js 统一对外导出。

```js // 从 './lib.js' 中导出 hello 和默认导出并重命名为 libData export { hello, default as libData } from './lib.js'

// 从 './util.js' 中导出所有命名导出 export * from './util.js'

// 从 './util.js' 中默认导出并重命名为 utilData export { default as utilData } from './util.js' ```

使用的时候,就统一通过 index.js 引入即可。

```js // usage.js import { hello, byebye, libData, utilData } from './index.js'

hello(libData.filename) byebye(utilData.filename) ```

2 代码转换为 CJS

由于历史原因,大部分 Node 第三方模块还是 cjs 格式的 (近几年一些新包会同时提供 CJS 和 ESM 两种格式),

因此用户项目中也很少配置 type:"module",通常还是会通过构建工具将代码转换为 CJS 对外提供。

下面介绍几个上手比较简单的工具

测试代码也比较简单,如下。

export.js

``js export function hello(name) { console.log(Hello, ${name}!`) return name }

export default { name: 'hello world' } ```

index.js

```js import defaultValue, { hello } from './export.js'

const result = hello(defaultValue.name)

export { result } ```

2.1 tsup

https://github.com/egoist/tsup

直接通过 npx 指令调用,或者先全局安装再使用 npm i -g tsup

sh npx tsup packages/3/3.2/esm2cjs/index.js --format cjs

转换后的结果 dist/index.js 如下。

2.2 ncc

https://github.com/vercel/ncc

先通过 npm 全局安装。

sh npm i -g @vercel/ncc

安装完后可以查看版本信息。

调用打包。

sh ncc build packages/3/3.2/esm2cjs/index.js

tip:确保目标文件所在目录下的 package.json 中没有 type 字段

打包后的结果如下。

2.3 更多

当然上面针对 tsupncc 都只做了简单的介绍,更多用法还需查阅对应的文档。

除此之外常用的还可以使用 babelRollupWebpack 等等这里就不再展开了,感兴趣的同学可以下去自行了解。

小结

ES Modules (ESM) 模块系统是在 ECMAScript 6 (ES2015/ES6) 中引入的一项重要特性,旨在取代 CommonJS 和 AMD 规范,成为 JavaScript 模块化的主要标准。

为了使 Node.js 正确识别 ESM 模块,通常有 2 种方式:

ES Modules 中的导入导出有多种用法,主要介绍了以下 4 种使用场景:

最后介绍了 2 种将 ESM 模块转换为 CJS 模块的工具,tsupncc